﻿#include "scanner.h"
#include "ui_scanner.h"
#include "image.h"




Scanner::Scanner(const QString& fileName, CenteredWidget *parent) :
    QDialog(parent),
    ui (new Ui::Scanner)
{
    QApplication::setOverrideCursor (QCursor(Qt::WaitCursor));

    ui->setupUi(this);

    setWindowFlags (Qt::Window
                    | Qt::CustomizeWindowHint
                    | Qt::WindowTitleHint
                    | Qt::WindowMaximizeButtonHint
                    | Qt::WindowMinimizeButtonHint);

    m_toolBox = ui->toolBox;
    QStyle* style = qApp->style ();


    ui->removeMargins->setEnabled (false);
    ui->Ok->setEnabled (false);
    ui->Ok->setIcon (Config::appIcon ());
    ui->cancel->setEnabled (true);
    ui->cancel->setIcon (style->standardIcon (QStyle::SP_DialogCancelButton));

    ui->acquire->setIcon (QIcon::fromTheme ("scanner",
                                            QIcon (":/images/scanner.svg")));
    ui->copyMessages->setEnabled (false);
    ui->copyMessages->setIcon (QIcon::fromTheme ("edit-copy", QIcon (":/images/edit-copy.png")));

    m_fileName = fileName;
    ui->tabWidget->setCurrentIndex (0);
    ui->tabWidget->setTabIcon (MESSAGE_TAB, QIcon ());

    Config config (0);

    m_showOnlyFirstGroup = !config.optionAllSaneOptions ();
    m_currentHandle = 0;

    clearToolBox ();
    initSettings ();

    m_saneOK = initSane ();
    if (m_saneOK)
    {
        connect (this,
                 SIGNAL(message(QString)),
                 this,
                 SLOT (displayMessage (QString)));

        connect (ui->deviceList,
                 SIGNAL(currentIndexChanged(int)),
                 this,
                 SLOT(onDeviceChanged(int)));

        ui->deviceList->clear ();

        QHash<QString, int> fd = foundDevices ();

        m_saneOK = !fd.isEmpty ();

        ui->acquire->setDisabled (true);
        if (m_saneOK)
        {
            foreach (QString txt, fd.keys ())
            {
                ui->deviceList->addItem (txt, fd.value (txt));
            }


            BasicScene* scene = new BasicScene (this);
            scene->setBackgroundBrush (QBrush (Qt::lightGray));
            m_pixmapItem = new QGraphicsPixmapItem (QPixmap (Config::appIconPath ()));

            m_part = new ImagePart (QRect (10,10,10,10), 2, 0);
            m_part->setRoleMax (2);
            m_part->setRole (2);
            m_part->setVisible (false);

            scene->addItem (m_pixmapItem);
            scene->addItem (m_part);

            ui->view->setScene (scene);

            connect (this,
                     SIGNAL(imageIsAvailable()),
                     this,
                     SLOT(onImageIsAvailable()));

            ui->deviceList->setCurrentIndex (0);
        }
    }
    setScanning (false);
    QApplication::restoreOverrideCursor ();
}
//-------------------
Scanner::~Scanner()
{
    delete ui;
    if (m_currentHandle != 0) {
        sane_close (m_currentHandle);
    }
    sane_exit ();	// ferme les handles ouverts
}
//---------------------------
void Scanner::initSettings ()
{
    m_settings = new QSettings (qApp->organizationName (), Config::scanAppName (), this);
}
//-------------------------------------
void Scanner::readSettings ()
{
    if (m_currentHandle == 0) return;
    m_settings->beginGroup (QString (m_currentDevice->model));
    foreach (SaneOption* d, m_saneOptions)
    {
        QString val (m_settings->value (d->name (), QString ()).toString ());
        if (!val.isEmpty ())
        {
            d->setGuiValue (val);
        }
    }
    m_settings->endGroup ();
}
//----------------------------
void Scanner::writeSettings ()
{
    m_settings->beginGroup (QString (m_currentDevice->model));
    foreach (SaneOption* d, m_saneOptions) {
        m_settings->setValue (d->name (), d->guiStrValue ());
    }
    m_settings->endGroup ();
}
//-----------------------
bool Scanner::initSane ()
{
    SANE_Int version;
    bool ok = verifyStatus (sane_init (&version, 0)) == SANE_STATUS_GOOD;
    return ok;
}
//------------------------------------------------
QHash<QString, int> Scanner::foundDevices ()
{
    QHash<QString, int> result;
    if (verifyStatus (sane_get_devices (m_devices, false)) != SANE_STATUS_GOOD) return result;

    int index = 0;
    const SANE_Device* device = (*m_devices) [index];

    while (device) {
        QString readable = QString ("%1 %2 (%3)")
                .arg (device->vendor)
                .arg (device->model)
                .arg (device->type);

        message (tr ("Appareil détecté : %1 [%2]").arg (readable).arg (device->name));

        result.insert (readable,
                       index);

        index++;
        device = (*m_devices) [index];
    }

    return result;
}
//-------------------------------------------------------------------------
void Scanner::onDeviceChanged (const int listIndex)
{
    if (!m_saneOK) return;

    int index = ui->deviceList->itemData (listIndex).toInt ();

    m_currentDevice = (*m_devices) [index];
    SANE_String_Const name = m_currentDevice->name;
    m_isHPdevice = (QString (m_currentDevice->vendor).contains ("Hewlett"));
    if (m_currentHandle != 0)
    {
        sane_close (m_currentHandle);
        m_currentHandle = 0;
    }

    m_saneOK = verifyStatus (sane_open (name, &m_currentHandle)) == SANE_STATUS_GOOD;

    if (!m_saneOK)
    {
        m_currentHandle = 0;
    }

    ui->acquire->setEnabled (m_saneOK);

    if (m_saneOK)
    {
        getAllSaneOptions ();
        readSettings ();
    }

}
//---------------------------
void Scanner::clearToolBox ()
{
    for (int index = m_toolBox->count () - 1 ; index >= 0 ; --index) {
        QWidget* tab = m_toolBox->widget (index);
        m_toolBox->removeItem (index);
        delete tab;
    }
    QApplication::processEvents ();
    m_saneOptions.clear ();
}
//----------------------------------
void Scanner::getAllSaneOptions ()
{

    clearToolBox ();
    const SANE_Option_Descriptor* descr;

    int index = 0;
    int groupFound = 0;
    QWidget* tab = 0;
    QVBoxLayout* currentLayout = 0;
    do
    {
        // recherche des descripteurs
        descr = sane_get_option_descriptor (m_currentHandle, index);
        if (descr == 0) break;

        QString name = descr->name;
        if (descr->type == SANE_TYPE_GROUP)
        {
            if (m_toolBox->count () != 0)
                // terminer le groupe précédent par un spacer
            {
                currentLayout->addStretch ();
                if (m_showOnlyFirstGroup)
                    break;
            }
            groupFound++;
            tab = new QWidget (m_toolBox);
            if (tab->layout () != 0) {
                delete tab->layout ();
            }
            currentLayout = new QVBoxLayout (tab);
            currentLayout->setContentsMargins (0,5,0,1);

            m_toolBox->addItem (tab, QIcon (":/images/preferences-desktop.png"), QString("%1")
                                .arg (descr->title));
        }
        else
        {
            if (descr->type != SANE_TYPE_BUTTON) // on ne traite pas les type BUTTON, qui n'ont pas de valeur
            {
                bool isIntVector = (descr->type == SANE_TYPE_INT || descr->type == SANE_TYPE_FIXED)
                        && descr->size != sizeof (SANE_Word) ;

                if (!name.isEmpty () && !isIntVector) // si le nom n'est pas vide et si ce n'est pas un vecteur d'entiers
                {
                    SaneOption* devOpt = new SaneOption (tab,
                                                         m_currentHandle,
                                                         index,
                                                         descr);
                    currentLayout->addWidget (devOpt);
                    m_saneOptions << devOpt;
                }
            }
        }
        index++;
    }
    while (true);

    if (m_toolBox->count () != 0) {	// terminer le groupe précédent par un spacer
        currentLayout->addStretch ();
    }

    // on a tout : on peut brancher les signaux et les slots

    foreach (SaneOption* d, m_saneOptions) {
        connect (d,
                 SIGNAL(mustReloadOptions()),
                 this,
                 SLOT(reloadAllOptions()));
    }
}
//------------------------------------------------------------------
SANE_Status Scanner::verifyStatus (SANE_Status status, QString info)
{
    QString errorMsg;
    switch (status)
    {
    case SANE_STATUS_GOOD: /* everything A-OK */
        break;

    case SANE_STATUS_UNSUPPORTED: /* operation is not supported */
        errorMsg = tr ("Opération non supportée");
        break;

    case SANE_STATUS_CANCELLED: /* operation was cancelled */
        errorMsg = tr ("Opération annulée");
        break;

    case SANE_STATUS_DEVICE_BUSY: /* device is busy; try again later */
        errorMsg = tr ("Périphérique occupé");
        break;

    case SANE_STATUS_INVAL: /* data is invalid (includes no dev at open) */
        errorMsg = tr ("Donnée invalide");
        break;

    case SANE_STATUS_EOF: /* no more data available (end-of-file) */
        errorMsg = tr ("Données supplémentaires indisponibles");
        break;

    case SANE_STATUS_JAMMED: /* document feeder jammed */
        errorMsg = tr ("Erreur dans l'alimentation en documents");
        break;

    case SANE_STATUS_NO_DOCS: /* document feeder out of documents */
        errorMsg = tr ("Un document est requis");
        break;

    case SANE_STATUS_COVER_OPEN: /* scanner cover is open */
        errorMsg = tr ("Le capot du scanner est ouvert");
        break;

    case SANE_STATUS_IO_ERROR: /* error during device I/O */
        errorMsg = tr ("Erreur pendant un échange avec le périphérique");
        break;

    case SANE_STATUS_NO_MEM: /* out of memory */
        errorMsg = tr ("Dépassement de capacité mémoire");
        break;

    case SANE_STATUS_ACCESS_DENIED: /* access to resource has been denied */
        errorMsg = tr ("Accès refusé au périphérique");
        break;

    default:
        break;
    }

    if (!errorMsg.isEmpty ())
    {
        if (!info.isEmpty ())
            sendMessage (QString ("%1 -> %2")
                         .arg (info)
                         .arg (errorMsg)
                         );
        else
            sendMessage (errorMsg);
    }

    return status;
}
//---------------------------------------------
void Scanner::sendMessage (const QString& msg)
{
    emit message (msg);
    QApplication::processEvents ();
}
//-----------------------------------------------
void Scanner::displayMessage(const QString& msg)
{
    m_messages << msg;
    ui->messageList->clear ();
    ui->messageList->addItems (m_messages);
    ui->tabWidget->setTabIcon (MESSAGE_TAB, QIcon (":/images/indicator-messages-new.png"));
    ui->copyMessages->setEnabled (!m_messages.isEmpty ());
    QApplication::processEvents ();
}
//---------------------------------
void Scanner::onImageIsAvailable ()
{
    m_pixmapItem->setPixmap (QPixmap::fromImage (m_imageFromScanner));
    ui->removeMargins->setEnabled (true);
    on_removeMargins_toggled (ui->removeMargins->isChecked ());
    m_part->setVisible (true);
    QRectF rectF = m_pixmapItem->boundingRect ().adjusted (-25,-25,25,25);
    ui->view->fitInView (rectF, Qt::KeepAspectRatio);
    QApplication::processEvents ();

}
//--------------------------------------------------------------------
bool Scanner::saveImage (const QImage& image)
{
    if (!image.save (m_fileName, "TIFF", 100))
    {
        sendMessage (tr ("L'enregistrement au format %1 a échoué")
                     .arg ("TIFF"));
        return false;
    }
    return true;
}
//--------------------------------------------------
void Scanner::on_tabWidget_currentChanged (const int index)
{
    if (index == MESSAGE_TAB)
    {
        ui->tabWidget->setTabIcon (MESSAGE_TAB, QIcon ());
    }
}
//----------------------------------------------------
QString Scanner::strValue (const QString& optionName)
{
    foreach (SaneOption* d, m_saneOptions) {
        if (d->name () == optionName) {
            return d->guiStrValue ();
        }
    }
    return QString ();
}

void Scanner::disableAllOptions()

{
    foreach (SaneOption* d, m_saneOptions) {
        d->setDisabled ();
    }
    ui->deviceList->setDisabled (true);
}
//--------------------------------
void Scanner::reloadAllOptions ()
{
    foreach (SaneOption* d, m_saneOptions)
    {
        if ((QObject *) d->thisWidget () != sender ())
        {
            d->deviceToGui ();
        }
    }
}
//--------------------------------------
void Scanner::on_copyMessages_clicked()
{
    QString msg = m_messages.join ("\n");
    QApplication::clipboard ()->setText (msg);
}
//---------------------------------
void Scanner::on_acquire_clicked ()
{
    setScanning (true);
    ui->acquire->setEnabled (false);
    ui->Ok->setEnabled (false);
    ui->cancel->setEnabled (false);
    // bloquer l'interface graphique
    disableAllOptions ();
    QApplication::setOverrideCursor (QCursor(Qt::WaitCursor));
    QApplication::processEvents ();

    // créer la ligne de commande
    QString commandLine ("scanimage");
    //	commandLine += QString (" -d \"%1\"").arg (m_currentDevice->name);
    commandLine += QString (" --device-name=\"%1\"").arg (m_currentDevice->name);

    // isoler les options spécifiques (x, y, w, h de la portion à scanner)
    QStringList specificOptions;
    specificOptions << SANE_NAME_SCAN_TL_X << SANE_NAME_SCAN_TL_Y << SANE_NAME_SCAN_BR_X << SANE_NAME_SCAN_BR_Y;
    bool specific = false;

    foreach (SaneOption* d, m_saneOptions)
    {
        if (d->isActive ())
        {
            QString name = d->name ();
            if (specificOptions.contains (name))
            {
                specific = true;
            }
            else
            {
                QString value (d->guiStrValue ());

                value = QString ("\"%1\"").arg (value);
                commandLine += QString (" --%1=%2")
                        .arg (name)
                        .arg (value)
                        ;
            }
        }
    }

    if (specific)
    {
        QString valL = strValue (SANE_NAME_SCAN_TL_X);
        QString valT = strValue (SANE_NAME_SCAN_TL_Y);
        QString valX = QString ("%1")
                .arg (strValue (SANE_NAME_SCAN_BR_X).toInt () - strValue (SANE_NAME_SCAN_TL_X).toInt ());
        QString valY = QString ("%1")
                .arg (strValue (SANE_NAME_SCAN_BR_Y).toInt () - strValue (SANE_NAME_SCAN_TL_Y).toInt ());
        commandLine += QString (" -l %1 -t %2 -x %3 -y %4")
                .arg (valL)
                .arg (valT)
                .arg (valX)
                .arg (valY);

    }
    QString format = "tiff";

    commandLine += QString (" --format=%1").arg (format);

    // afficher la ligne de commande dans la console
    message (commandLine);
    qDebug () << tr ("Exécute : %1").arg (commandLine);

    // création et préparation du process
    m_process = new QProcess (this);
    connect (m_process,
             SIGNAL(finished(int,QProcess::ExitStatus)),
             this,
             SLOT(onProcessFinished(int,QProcess::ExitStatus)));

    connect (m_process,
             SIGNAL(error(QProcess::ProcessError)),
             this,
             SLOT(onProcessError(QProcess::ProcessError)));

    connect (m_process,
             SIGNAL(readyReadStandardError()),
             this,
             SLOT(onProcessRRSE()));

    // redirection : ">" dans la ligne de commande semble ne pas fonctionner avec QProcess
    m_process->setStandardOutputFile (m_fileName);

    if (!isHPdevice () && m_currentHandle)	{ // fermer le handle pour ne pas être en concurrence avec scanimage
        sane_close (m_currentHandle);
        m_currentHandle = 0;
    }

    // démarrer scanimage
    m_process->start (commandLine);
    if (!m_process->waitForStarted ()) {
        m_process->close ();
    }

    // la suite des opérations se passe dans la fonction onProcessFinished

}
//--------------------------------------------------------------------------
void Scanner::onProcessFinished (const int result, const QProcess::ExitStatus /*status*/)
{
    // si pas d'erreur c'est qu'une image est dispo
    if (result == 0) {
        m_imageFromScanner.load (m_fileName);
        emit imageIsAvailable ();
        writeSettings ();	// n'enregistre la config que lorsque tout s'est bien passé
    }

    if (m_currentHandle == 0) {
        //	 redemander un handle
        if (verifyStatus (sane_open (m_currentDevice->name, &m_currentHandle)) != SANE_STATUS_GOOD) {
            // si pas Ok le mettre à 0
            m_currentHandle = 0;
        }
    }

    if (m_currentHandle != 0) {
        //		// l'affecter à toutes les options
        //		resetHandle (m_currentHandle);
        //		// débloquer l'interface graphique
        //		enableActiveOptions ();
        //		// autoriser une nouvelle numérisation
        ui->acquire->setEnabled (true);
    }

    ui->cancel->setEnabled (true);
    if (result == 0 && m_currentHandle) {
        ui->Ok->setEnabled (true);
    }

    getAllSaneOptions ();
    ui->deviceList->setEnabled (true);
    QApplication::processEvents ();
    setScanning (false);

    QApplication::restoreOverrideCursor ();
}
//--------------------------------------------------------------
void Scanner::onProcessError(QProcess::ProcessError /*error*/)
{
    onProcessRRSE ();

    m_process->close ();
}
//---------------------------
void Scanner::onProcessRRSE()
{
    message (QString (m_process->readAllStandardError ()));
}
//------------------------------
void Scanner::on_Ok_clicked()
{
    if (saveImage (m_imageFromScanner.copy (m_part->rectangle ())))
        accept ();
    else
        reject ();
}
//--------------------------------
void Scanner::on_cancel_clicked()
{
    reject ();
}
//---------------------------------------------------
void Scanner::on_removeMargins_toggled (const bool checked)
{
    m_part->setMaximumSize (m_pixmapItem->boundingRect ().size ());

    QRectF result (5.,5.,5.,5.);
    if (checked)
    {
//        QRectF roiRect = m_imageFromScanner.roiRect32 (qRgb (255,255,255));
//        if (roiRect.isValid ()) {
//            result = roiRect;
//        }
    }
    else {
        result = m_pixmapItem->boundingRect ();
    }

    m_part->setGeometry (result);
}
